package com.top.application.controller;

import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;

import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.top.application.entity.ClientEntity;
import com.top.application.entity.OauthTokenEntity;
import com.top.application.entity.ResourceEntity;
import com.top.application.entity.RoleEntity;
import com.top.application.entity.SecurityKeyEntity;
import com.top.application.entity.UserEntity;
import com.top.application.service.ClientService;
import com.top.application.service.OauthTokenService;
import com.top.application.service.ResourceService;
import com.top.application.service.RoleService;
import com.top.application.service.SecurityService;
import com.top.application.service.UserService;
import com.top.application.service.UserTokenService;
import com.top.application.utils.ConfigUtils;
import com.top.application.utils.HMACmd5;
import com.top.application.utils.PemUtils;

/**
 * 
 * @ClassName: OauthTokenController
 * @Description: TODO(授权控制层)
 * @author zhaiey
 * @date 2018年4月9日 下午1:55:17
 *
 */
@Controller
public class OauthTokenController {
	public static Logger log = LoggerFactory.getLogger(OauthTokenController.class);
	
	private static final String PRIVATE_KEY_FILE = ConfigUtils.getPropertiesByKey("pem.private");
	private static final String PUBLIC_KEY_FILE = ConfigUtils.getPropertiesByKey("pem.public");
	private static RSAPublicKey publicKey;
	private static RSAPrivateKey privateKey;
	@Autowired
	private UserService userService;
	@Autowired
	private ClientService clientService;
	@Autowired
	private OauthTokenService oauthTokenService;
	@Autowired
	private RoleService roleService;
	@Autowired
	private ResourceService resourceService;
	@Autowired
	private UserTokenService userTokenService;
	@Autowired
	private SecurityService securityService;

	@ApiOperation(value = "服务授权接口", notes = "根据授权类型对需要授权的客户端进行服务鉴权")
	@ApiImplicitParams({
			@io.swagger.annotations.ApiImplicitParam(name = "open_id", value = "授权类型", required = true, dataType = "String"),
			@io.swagger.annotations.ApiImplicitParam(name = "app_id", value = "用户名", required = false, dataType = "String"),
			@io.swagger.annotations.ApiImplicitParam(name = "refresh_token", value = "密码", required = false, dataType = "String") })
	@RequestMapping(value = { "/api/oauth/token" }, method = { org.springframework.web.bind.annotation.RequestMethod.GET })
	@ResponseBody
	public JSONObject token(HttpServletRequest request,
			HttpServletResponse response, String open_id, String app_id,
			String refresh_token, String username, String password) {
		JSONObject result = new JSONObject();
		if (StringUtils.isNotEmpty(refresh_token)) {
			result = refreshToken(refresh_token);
		} else if ((StringUtils.isNotEmpty(open_id))
				&& (StringUtils.isNotEmpty(app_id))) {
			result = openidTypeToken(open_id, app_id);
		} else if ((StringUtils.isNotEmpty(username))
				&& (StringUtils.isNotEmpty(password))) {
			result = passwordTypeToken(username, password);
		} else {
			result.put("code", "303");
			result.put("msg", "参数有误");
		}
		return result;
	}

	public JSONObject openidTypeToken(String open_id, String app_id) {
		JSONObject result = new JSONObject();
		Map<String, Object> securityParam = new HashMap<String, Object>();
		securityParam.put("openId", open_id);
		securityParam.put("appId", app_id);
		try {
			SecurityKeyEntity securityKey = this.securityService
					.selectByMap(securityParam);
			if (securityKey != null) {
				Map<String, Object> oauthParam = new HashMap<String, Object>();
				oauthParam.put("securityId", securityKey.getId());
				OauthTokenEntity oauthToken = this.oauthTokenService
						.selectByMap(oauthParam);
				if (oauthToken != null) {
					result.put("access_token", oauthToken.getAccessToken());
					result.put("token_id", oauthToken.getTokenId());
					result.put("refresh_token", oauthToken.getRefreshToken());
					result.put("expires_in", "7200");
					result.put("scope", "api");
				} else {
					result = appidCreateToken(securityKey);
				}
			} else {
				result.put("code", "303");
				result.put("msg", "illegal app,please check your request");
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return result;
	}

	public JSONObject passwordTypeToken(String username, String password) {
		JSONObject result = new JSONObject();
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		try {
			UserEntity userEntity = this.userService.getByUserName(username);
			if (userEntity != null) {
				publicKey = (RSAPublicKey) PemUtils.readPublicKeyFromFile(
						PUBLIC_KEY_FILE, "RSA");
				privateKey = (RSAPrivateKey) PemUtils.readPrivateKeyFromFile(
						PRIVATE_KEY_FILE, "RSA");
				Algorithm algorithm = Algorithm.RSA256(publicKey, privateKey);

				String accessToken = JWT.create().withIssuer("auth0")
						.withClaim("user_name", userEntity.getUserName())
						.withClaim("user_id", userEntity.getId())
						.withClaim("type", "user")
						.withClaim("create_time", sdf.format(new Date()))
						.sign(algorithm);

				String refresh_token = JWT.create().withIssuer("auth0")
						.withClaim("user_name", userEntity.getUserName())
						.withClaim("user_id", userEntity.getId())
						.withClaim("type", "user")
						.withClaim("create_time", sdf.format(new Date()))
						.sign(algorithm);
				String token_id = HMACmd5.encryptHmacMD5(accessToken, "topgun");
				OauthTokenEntity oauthTokenEntity = new OauthTokenEntity();
				oauthTokenEntity.setAccessToken(accessToken);
				oauthTokenEntity.setClientType("user");
				oauthTokenEntity.setCreateTime(new Date());
				oauthTokenEntity.setIsDelete(Short.valueOf((short) 0));
				oauthTokenEntity.setRefreshToken(refresh_token);
				oauthTokenEntity.setStatus(Short.valueOf((short) 2));
				oauthTokenEntity.setTokenId(token_id);
				this.oauthTokenService.insert(oauthTokenEntity);
				result.put("access_token", accessToken);
				result.put("token_id", token_id);
				result.put("refresh_token", refresh_token);
				result.put("expires_in", Integer.valueOf(7200));
				result.put("scope", "user");
			} else {
				result.put("code", "303");
				result.put("msg", "illegal user,please check your request");
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return result;
	}

	public JSONObject clientTypeToken(String uuid) {
		JSONObject result = new JSONObject();
		try {
			ClientEntity clientEntity = this.clientService.selectByUuid(uuid);
			if (clientEntity != null) {
				Map<String, Object> param = new HashMap<String, Object>();
				param.put("userId", clientEntity.getId());
				OauthTokenEntity oauthToken = this.oauthTokenService
						.selectByMap(param);
				if (oauthToken != null) {
					result.put("access_token", oauthToken.getAccessToken());
					result.put("token_id", oauthToken.getTokenId());
					result.put("refresh_token", oauthToken.getRefreshToken());
					result.put("expires_in", "7200");
					result.put("scope", oauthToken.getClientType());
				} else {
					result = clientCreateToken(clientEntity);
				}
			} else {
				result.put("code", "303");
				result.put("msg", "illegal user,please check your request");
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return result;
	}

	public JSONObject appidCreateToken(SecurityKeyEntity securityKey) {
		JSONObject result = new JSONObject();
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		try {
			publicKey = (RSAPublicKey) PemUtils.readPublicKeyFromFile(
					PUBLIC_KEY_FILE, "RSA");
			privateKey = (RSAPrivateKey) PemUtils.readPrivateKeyFromFile(
					PRIVATE_KEY_FILE, "RSA");
			Algorithm algorithm = Algorithm.RSA256(publicKey, privateKey);

			String accessToken = JWT.create().withIssuer("auth0")
					.withClaim("app_id", securityKey.getAppId())
					.withClaim("id", String.valueOf(securityKey.getId()))
					.withClaim("secretKey", securityKey.getSecretKey())
					.withClaim("type", "api")
					.withClaim("create_time", sdf.format(new Date()))
					.sign(algorithm);

			String refresh_token = JWT.create().withIssuer("auth0")
					.withClaim("app_id", securityKey.getAppId())
					.withClaim("open_id", securityKey.getOpenId())
					.withClaim("secretKey", securityKey.getSecretKey())
					.withClaim("type", "api")
					.withClaim("create_time", sdf.format(new Date()))
					.sign(algorithm);
			String token_id = HMACmd5.encryptHmacMD5(accessToken, "topgun");
			OauthTokenEntity oauthTokenEntity = new OauthTokenEntity();
			oauthTokenEntity.setAccessToken(accessToken);
			oauthTokenEntity.setClientType("api");
			oauthTokenEntity.setCreateTime(new Date());
			oauthTokenEntity.setIsDelete(Short.valueOf((short) 0));
			oauthTokenEntity.setRefreshToken(refresh_token);
			oauthTokenEntity.setStatus(Short.valueOf((short) 1));
			oauthTokenEntity.setTokenId(token_id);
			oauthTokenEntity.setSecurityId(securityKey.getId());

			this.oauthTokenService.deleteBySecurityId(securityKey.getId());
			this.oauthTokenService.insert(oauthTokenEntity);
			result.put("access_token", accessToken);
			result.put("token_id", token_id);
			result.put("refresh_token", refresh_token);
			result.put("expires_in", Integer.valueOf(7200));
			result.put("scope", "api");
		} catch (Exception e) {
			e.printStackTrace();
		}
		return result;
	}

	public JSONObject clientCreateToken(ClientEntity clientEntity) {
		JSONObject result = new JSONObject();
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		try {
			publicKey = (RSAPublicKey) PemUtils.readPublicKeyFromFile(
					PUBLIC_KEY_FILE, "RSA");
			privateKey = (RSAPrivateKey) PemUtils.readPrivateKeyFromFile(
					PRIVATE_KEY_FILE, "RSA");
			Algorithm algorithm = Algorithm.RSA256(publicKey, privateKey);

			String accessToken = JWT.create().withIssuer("auth0")
					.withClaim("id", String.valueOf(clientEntity.getId()))
					.withClaim("type", clientEntity.getScope())
					.withClaim("create_time", sdf.format(new Date()))
					.sign(algorithm);

			String refresh_token = JWT.create().withIssuer("auth0")
					.withClaim("name", clientEntity.getClientName())
					.withClaim("id", String.valueOf(clientEntity.getId()))
					.withClaim("type", clientEntity.getScope())
					.withClaim("create_time", sdf.format(new Date()))
					.sign(algorithm);
			String token_id = HMACmd5.encryptHmacMD5(accessToken, "topgun");
			OauthTokenEntity oauthTokenEntity = new OauthTokenEntity();
			oauthTokenEntity.setAccessToken(accessToken);
			oauthTokenEntity.setClientType(clientEntity.getScope());
			oauthTokenEntity.setCreater(clientEntity.getClientName());
			oauthTokenEntity.setCreateTime(new Date());
			oauthTokenEntity.setIsDelete(Short.valueOf((short) 0));
			oauthTokenEntity.setRefreshToken(refresh_token);
			oauthTokenEntity.setStatus(Short.valueOf((short) 1));
			oauthTokenEntity.setTokenId(token_id);
			oauthTokenEntity.setSecurityId(clientEntity.getId());
			this.oauthTokenService.deleteBySecurityId(clientEntity.getId());
			this.oauthTokenService.insert(oauthTokenEntity);
			result.put("access_token", accessToken);
			result.put("token_id", token_id);
			result.put("refresh_token", refresh_token);
			result.put("expires_in", Integer.valueOf(7200));
			result.put("scope", clientEntity.getScope());
		} catch (Exception e) {
			e.printStackTrace();
		}
		return result;
	}

	public JSONObject refreshToken(String refresh_token) {
		JSONObject result = new JSONObject();
		try {
			OauthTokenEntity oauthToken = this.oauthTokenService
					.resentRefreshToken(refresh_token);
			if (oauthToken == null) {
				result.put("code", "302");
				result.put("msg","refresh_token is not exist,please check your request");
				return result;
			}
			DecodedJWT jwt = JWT.decode(refresh_token);
			Map<String, Claim> claims = jwt.getClaims();
			publicKey = (RSAPublicKey) PemUtils.readPublicKeyFromFile(
					PUBLIC_KEY_FILE, "RSA");
			privateKey = (RSAPrivateKey) PemUtils.readPrivateKeyFromFile(
					PRIVATE_KEY_FILE, "RSA");
			Algorithm algorithm = Algorithm.RSA256(publicKey, privateKey);

			String accessToken = JWT
					.create()
					.withIssuer("auth0")
					.withClaim("app_id",
							((Claim) claims.get("app_id")).asString())
					.withClaim("open_id",
							((Claim) claims.get("open_id")).asString())
					.withClaim("type", ((Claim) claims.get("type")).asString())
					.withClaim("create_time", new Date()).sign(algorithm);
			String token_id = HMACmd5.encryptHmacMD5(accessToken, "topgun");
			OauthTokenEntity refreshOauthToken = new OauthTokenEntity();
			refreshOauthToken.setAccessToken(accessToken);
			refreshOauthToken.setTokenId(token_id);
			refreshOauthToken.setCreateTime(new Date());
			refreshOauthToken.setModifyTime(new Date());
			refreshOauthToken.setId(oauthToken.getId());
			this.oauthTokenService.update(refreshOauthToken);
			result.put("access_token", accessToken);
			result.put("token_id", token_id);
			result.put("refresh_token", refresh_token);
			result.put("expires_in", Integer.valueOf(7200));
			result.put("scope", ((Claim) claims.get("type")).asString());
		} catch (Exception e) {
			e.printStackTrace();
		}
		return result;
	}

	@ApiOperation(value = "获取资源信息", notes = "根据access_token或者token_id获取资源信息")
	@ApiImplicitParams({
			@io.swagger.annotations.ApiImplicitParam(name = "token_id", value = "", required = false, dataType = "String"),
			@io.swagger.annotations.ApiImplicitParam(name = "access_token", value = "", required = false, dataType = "String") })
	@RequestMapping(value = { "/api/oauth/respurce" }, method = { org.springframework.web.bind.annotation.RequestMethod.GET })
	@ResponseBody
	public JSONObject respurce(HttpServletRequest request,
			HttpServletResponse response) {
		JSONObject result = new JSONObject();
		String access_token = (String) request.getAttribute("access_token");
		String token_id = (String) request.getAttribute("token_id");
		Map<String, Object> param = new HashMap<String, Object>();
		if ((StringUtils.isEmpty(token_id))
				&& (StringUtils.isEmpty(access_token))) {
			result.put("code", "303");
			result.put("msg", "illegal is not exist,please check your request");
			return result;
		}
		if ((StringUtils.isEmpty(token_id))
				&& (!StringUtils.isEmpty(access_token))) {
			param.put("access_token", access_token);
		} else if ((!StringUtils.isEmpty(token_id))
				&& (StringUtils.isEmpty(access_token))) {
			param.put("access_token", access_token);
		}
		try {
			OauthTokenEntity oauthTokenEntity = this.oauthTokenService
					.selectByMap(param);
			if (oauthTokenEntity == null) {
				result.put("code", "302");
				result.put("msg", "token is overtime,please check your request");
				return result;
			}
			JSONObject data = new JSONObject();
			List<Integer> roleIds = new ArrayList<Integer>();
			List<RoleEntity> roles = new ArrayList<RoleEntity>();
			if ("app".equals(oauthTokenEntity.getClientType())) {
				UserEntity userEntity = this.userService.selectById(oauthTokenEntity.getSecurityId());
				roles = this.roleService.getRoleValuesByUserId(userEntity.getId());
				data.put("scope", "app");
				data.put("username", userEntity.getUserName());
			} else {
				ClientEntity clientEntity = this.clientService.selectById(oauthTokenEntity.getSecurityId());
				roles = this.roleService.getRoleValuesByClientId(clientEntity.getId());
				data.put("scope", clientEntity.getScope());
				data.put("username", clientEntity.getUuid());
			}
			String role = "";
			for (RoleEntity item : roles) {
				roleIds.add(item.getId());
				role = role + "," + item.getRoleName();
			}
			data.put("roles", role);
			List<ResourceEntity> resources = this.resourceService.getPermissionsByRoleId(roleIds);
			JSONArray resource = paseResouToTreesJson(resources);
			data.put("resources", resource);
			result.put("code", "200");
			result.put("msg", "成功");
			result.put("data", data);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return result;
	}

	public JSONArray paseResouToTreesJson(List<ResourceEntity> resourceEntities) {
		JSONArray parents = new JSONArray();
		for (ResourceEntity parentItem : resourceEntities) {
			if (parentItem.getParentId().intValue() == 0) {
				JSONObject parent = new JSONObject();
				parent.put("resource_id", parentItem.getId());
				parent.put("resource_name", parentItem.getResourceName());
				parent.put("resource_url", parentItem.getResourceUrl());
				JSONArray childrens = new JSONArray();
				for (ResourceEntity childrenItem : resourceEntities) {
					JSONObject children = new JSONObject();
					if (childrenItem.getParentId() == parentItem.getId()) {
						children.put("resource_id", childrenItem.getId());
						children.put("resource_name",
								childrenItem.getResourceName());
						children.put("resource_url",
								childrenItem.getResourceUrl());
						childrens.add(children);
					}
				}
				parent.put("children_resource", childrens);
				parents.add(parent);
			}
		}
		return parents;
	}

	@ApiOperation(value = "校验token是否有效合法", notes = "根据access_token或者token_id校验token是否有效合法")
	@ApiImplicitParams({
			@io.swagger.annotations.ApiImplicitParam(name = "token_id", value = "", required = false, dataType = "String"),
			@io.swagger.annotations.ApiImplicitParam(name = "access_token", value = "", required = false, dataType = "String") })
	@RequestMapping(value = { "/api/oauth/confirm_token" }, method = { org.springframework.web.bind.annotation.RequestMethod.GET })
	@ResponseBody
	public JSONObject refreshToken(HttpServletRequest request,
			HttpServletResponse response) {
		JSONObject result = new JSONObject();
		String access_token = (String) request.getAttribute("access_token");
		String token_id = (String) request.getAttribute("token_id");
		Map<String, Object> param = new HashMap<String, Object>();
		if ((StringUtils.isEmpty(token_id))
				&& (StringUtils.isEmpty(access_token))) {
			result.put("code", "303");
			result.put("msg", "illegal is not exist,please check your request");
			return result;
		}
		if ((StringUtils.isEmpty(token_id))
				&& (!StringUtils.isEmpty(access_token))) {
			param.put("access_token", access_token);
		} else if ((!StringUtils.isEmpty(token_id))
				&& (StringUtils.isEmpty(access_token))) {
			param.put("token_id", token_id);
		}
		OauthTokenEntity oauthTokenEntity = this.oauthTokenService
				.selectByMap(param);
		if (oauthTokenEntity != null) {
			result.put("code", "200");
			result.put("msg", "The token is correct.");
		} else {
			result.put("code", "302");
			result.put("msg","token is overtime or wrong,please check your request");
			return result;
		}
		return result;
	}
}
